package com.zhuiYun.project.api.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhuiYun.project.api.order.entity.CommodityArr;
import com.zhuiYun.project.api.order.entity.SubmitOrderVo;
import com.zhuiYun.project.api.order.service.OrderService;
import com.zhuiYun.project.api.seckill.entity.SeckillDto;
import com.zhuiYun.project.api.seckill.entity.SeckillEntity;
import com.zhuiYun.project.api.seckill.mapper.SeckillDtoMapper;
import com.zhuiYun.project.api.seckill.mapper.SeckillMapper;
import com.zhuiYun.project.api.seckill.service.SeckillService;
import com.zhuiYun.project.common.errorCode.CommonException;
import com.zhuiYun.project.common.response.CommonConstants;
import com.zhuiYun.project.common.response.CommonResult;
import com.zhuiYun.project.config.RabbitMQConfig;
import com.zhuiYun.project.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName SeckillServiceImpl
 * @Description TODO
 * @Author lx
 * @Date 2023/4/3 15:00
 **/
@Service
@Slf4j
public class SeckillServiceImpl  implements SeckillService {

    private static final int TIMEOUT = 10 * 1000; // 超时时间，单位为毫秒
    private static final String LOCK_PREFIX = "seckill_lock_"; // 锁前缀
    private static final int LOCK_EXPIRE = 60 * 1000; // 锁过期时间，单位为毫秒

    @Autowired
    SeckillDtoMapper seckillDtoMapper;

    @Resource
    SeckillMapper seckillMapper;

    @Resource
    private RedisTemplate redisTemplate;

    @Autowired
    private OrderService orderService;

    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
    * @Author lx
    * @Description 首页展示5个限时抢购的商品
    * @Date 2023/4/3 19:30
    * @Return java.util.List<com.zhuiYun.project.api.seckill.entity.SeckillEntity>
    */
    @Override
    public List<SeckillDto> getSeckillAll() {
        SeckillDto seckillDto = new SeckillDto();
        List<SeckillDto> list = seckillDtoMapper.selectGetSeckillAll(seckillDto);
        return list;
    }

    /**
     * @Author lx
     * @Description 显示抢购商品信息
     * @Date 2023/4/3 19:30
     * @Return java.util.List<com.zhuiYun.project.api.seckill.entity.SeckillEntity>
     */
    @Override
    public List<SeckillDto> getSeckillList() {
        SeckillDto seckillDto = new SeckillDto();
        List<SeckillDto> list = seckillDtoMapper.selectGetSeckillList(seckillDto);
        return list;
    }

    /**
    * @Author lx
    * @Description 限时抢购
    * @Date 2023/4/6 9:23
    * @Return com.zhuiYun.project.common.response.CommonResult
    * @param userName 用户名
    * @param commId 商品id
    */
    @Override
    public CommonResult doSeckill(String userName, Integer commId) throws Exception {
        // 获取分布式锁
        String lockKey = LOCK_PREFIX + commId;
        String lockValue = UUID.randomUUID().toString();
        boolean locked = false;
        try{
            // 设置成功并返回true,否则设置失败并返回false
            locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
            if(!locked) {
                return CommonResult.error(500,"网络繁忙请稍后再试！");
            }
            //根据商品id查询商品库存
            //替换Redis拿到库存
            SeckillDto seckillDto = seckillDtoMapper.selectByCommId(commId);
            if(seckillDto.getSeckillStock()<=0){
                return CommonResult.error(500,"商品已售完");
            }
            // 扣减商品库存
            SeckillEntity seckill = new SeckillEntity();
            seckill.setId(seckillDto.getId());
            seckill.setSeckillStock(seckillDto.getSeckillStock()-1);
            seckillMapper.update(seckill);
            return CommonResult.error(500,"成功抢购");
        } finally {
            // 释放分布式锁
            if(locked){
                redisTemplate.delete(lockKey);
            }
        }
    }

    /**
    * @Description 使用redis分布式锁 提交订单
    * @Author lzy
    * @Date 2023/4/11 16:15
    * @Return com.zhuiYun.project.common.response.CommonResult
    * @param submitOrderVo
    */
    @Override
    public CommonResult submitHandworkDiyOrder(SubmitOrderVo submitOrderVo) throws CommonException {
        // 获取分布式锁
        String lockKey = LOCK_PREFIX + submitOrderVo.getUserId();
        String lockValue = UUID.randomUUID().toString();
        boolean locked = false;
        try{
            // 设置成功并返回true,否则设置失败并返回false
            locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
            if(!locked) {
                return CommonResult.error(500,"网络繁忙请稍后再试！");
            }
            return orderService.submitHandDiyOrder(submitOrderVo);
        } finally {
            // 释放分布式锁
            if(locked){
                redisTemplate.delete(lockKey);
            }
        }
    }
    /**
     *
     * @author: HKY
     * @date: 2023/4/23 18:53
     * @param submitOrderVo
     * @return com.zhuiYun.project.common.response.CommonResult
     * @Description: 使用redis预减库存和rabbitMQ异步生成订单
     */
    @Override
    public CommonResult submitCommodityOrder(SubmitOrderVo submitOrderVo) throws CommonException {
        //预减库存标识
        boolean flag = true;
        //商品名称
        StringBuffer commodityNameArr = new StringBuffer();
        //没库存的商品名称
        StringBuffer commodityMessage = new StringBuffer("非常抱歉，您所期望购买的：");
        List<CommodityArr> commodityArrs = JSON.parseArray(submitOrderVo.getProductArr(), CommodityArr.class);
        if (!commodityArrs.isEmpty()){
            for (CommodityArr commodityArr : commodityArrs) {
                commodityNameArr.append(commodityArr.getCommodityName()+"、");
                //如果存在秒杀商品库存key
                if (redisTemplate.hasKey(CommonConstants.COMMODITY_SECKILL_STOCK + commodityArr.getId())){
                    if (redisTemplate.opsForValue().decrement(CommonConstants.COMMODITY_SECKILL_STOCK+commodityArr.getId())<0){
                        flag =false;
                        commodityMessage.append(commodityArr.getCommodityName()+"、");
                    }
                    //如果存在普通商品库存key
                }else if (redisTemplate.hasKey(CommonConstants.COMMODITY_STOCK + commodityArr.getId())){
                    if (redisTemplate.opsForValue().decrement(CommonConstants.COMMODITY_STOCK+commodityArr.getId())<0){
                        flag =false;
                        commodityMessage.append(commodityArr.getCommodityName()+"、");
                    }
                    //都不存在说明商品秒杀时间结束
                } else {
                    flag =false;
                    commodityMessage.append(commodityArr.getCommodityName()+"、");
                }
            }
        }
        //所有商品名称
        commodityNameArr = commodityNameArr.deleteCharAt(commodityNameArr.length() - 1);
        log.info("参加秒杀的用户是：{}，秒杀的商品是：{}", submitOrderVo.getUserId(), commodityNameArr);
        //如果预减库存成功
        if (flag){
            //说明该商品的库存量有剩余，可以进行下订单操作
            log.info("用户：{}， 秒杀商品：{}，都有库存，可以进行下订单操作", submitOrderVo.getUserId(), commodityNameArr);
            //消息唯一标识（解决消息幂等性问题）
            String commoditySeckillSuccessFlag = CommonConstants.COMMODITY_SECKILL_SUCCESS_SOLE;
            //拼接消费成功唯一标识
            StringBuilder stringBuilder = new StringBuilder(commoditySeckillSuccessFlag);
            stringBuilder.append(new SimpleDateFormat("yyyyMMdd").format(new Date()));
            if (redisTemplate.opsForValue().get(commoditySeckillSuccessFlag) == null){
                //获取当天剩余的时间转化为秒，设置redis对应key值的失效时间
                int a= getRemainSecondsOneDay(new Date());
                redisTemplate.opsForValue().set(commoditySeckillSuccessFlag,0,a, TimeUnit.SECONDS);
            }
            stringBuilder.append(String.format("%08d",redisTemplate.opsForValue().increment(commoditySeckillSuccessFlag)));
            //存入实体类传输给rabbitMQ消费方
            submitOrderVo.setSuccessFlag(stringBuilder.toString());
            //发消息给订单消息队列，创建订单
            System.out.println( JSONObject.toJSONString(submitOrderVo));
            rabbitTemplate.convertAndSend(RabbitMQConfig.ORDER_EXCHANGE, RabbitMQConfig.ORDER_ROUTING_KEY, JSONObject.toJSONString(submitOrderVo));
            //将订单保存到redis 实现限购功能
            limitNumber(submitOrderVo.getUserId().toString(),commodityArrs);
             return CommonResult.ok( "秒杀" + commodityNameArr + "成功");
        }else {
            //Redis 库存回滚处理
            for (CommodityArr commodityArr : commodityArrs) {
                if (redisTemplate.hasKey(CommonConstants.COMMODITY_SECKILL_STOCK + commodityArr.getId())){
                    redisTemplate.opsForValue().increment(CommonConstants.COMMODITY_SECKILL_STOCK+commodityArr.getId());
                }else if (redisTemplate.hasKey(CommonConstants.COMMODITY_STOCK + commodityArr.getId())){
                    redisTemplate.opsForValue().increment(CommonConstants.COMMODITY_STOCK+commodityArr.getId());
                }
            }
            //说明该商品库存量没有剩余，直接返回秒杀失败的消息给用户
            log.info("ID为：{}的用户秒杀时商品的库存量没有剩余，秒杀结束",submitOrderVo.getUserId().toString());
            return CommonResult.error(500,commodityMessage.deleteCharAt(commodityMessage.length() - 1).append("已经没有库存:").toString());
        }

    }

    /**
     *
     * @author: HKY
     * @date: 2023/4/22 14:21
     * @param currentDate
     * @return java.lang.Integer
     * @Description: 获取当天剩余时间并转化成秒
     */
    public static Integer getRemainSecondsOneDay(Date currentDate) {
        //使用plusDays加传入的时间加1天，将时分秒设置成0
        LocalDateTime midnight = LocalDateTime.ofInstant(currentDate.toInstant(),
                        ZoneId.systemDefault()).plusDays(1).withHour(0).withMinute(0)
                .withSecond(0).withNano(0);
        LocalDateTime currentDateTime = LocalDateTime.ofInstant(currentDate.toInstant(),
                ZoneId.systemDefault());
        //使用ChronoUnit.SECONDS.between方法，传入两个LocalDateTime对象即可得到相差的秒数
        long seconds = ChronoUnit.SECONDS.between(currentDateTime, midnight);
        return (int) seconds;
    }
    /**
     *
     * @author: HKY
     * @date: 2023/4/14 15:52
     * @Description: 将订单保存到redis 实现限购功能（同一个用户对同一个商品的购买次数）
     */
    private void limitNumber(String userId, List<CommodityArr> commodityArrs){
        for (CommodityArr c :
                commodityArrs) {
            String extApiKey = "";
            extApiKey =extApiKey.concat(userId+":"+c.getCommodityName()+":number");
            RedisUtil.incr(extApiKey,1);
        }

    }
    /**
    * @Author lx
    * @Description 根据限时抢购商品点击查询限时抢购商品详细信息
    * @Date 2023/4/11 15:35
    * @Return java.util.List<com.zhuiYun.project.api.seckill.entity.SeckillDto>
    * @param seckillDto
    */
    @Override
    public List<SeckillDto> getSeckillMain(SeckillDto seckillDto) {
        return seckillDtoMapper.getSeckillMain(seckillDto);
    }

    /**
    * @Description 关闭秒杀
    * @Author lzy
    * @Date 2023/4/15 14:35
    * @Return void
    * @param commId
    */
    @Override
    public void ShutDownSeckill(String commId) {
        SeckillEntity seckillEntity = new SeckillEntity();
        seckillEntity.setCommId(Integer.valueOf(commId));
        seckillEntity.setDelFlag(1);
        seckillMapper.updateByPrimaryKeySelective(seckillEntity);
    }

    /**
    * @Description 将秒杀商品结束的时间存入redis
    * @Author lzy
    * @Date 2023/4/16 18:57
    * @Return void
    */
    @Override
    public void seckillOverTime() {
        SeckillEntity seckillEntity = new SeckillEntity();
        seckillEntity.setDelFlag(0);
        List<SeckillEntity> seckillEntityList = seckillMapper.select(seckillEntity);
        for (SeckillEntity seckill : seckillEntityList) {
            RedisUtil.set(CommonConstants.SECKILL + seckill.getCommId(), seckill);
            long now = System.currentTimeMillis();
            Date seckillEndDate = seckill.getSeckillEndDate();
            long overTime = (seckillEndDate.getTime() - now) / 1000;
            RedisUtil.set(CommonConstants.SECKILL + seckill.getCommId(), seckill, overTime);
        }
    }


}
